#!/usr/bin/env python
# -*- coding: utf-8 -*-

import weakref
import logging
from .connection import (Row, Connection, ConnectionPool, OperationalError)


class SQLClient(object):

    def __init__(self, host, port, db, user, password,
                 max_idle_time=None, connect_timeout=0,
                 charset='utf-8', sql_mode='TRADITIONAL',
                 connection_pool=None, **kwargs):
        if not connection_pool:
            args = dict(host=host, port=port, database=db, user=user,
                        password=password)
            if max_idle_time:
                args['max_idle_time'] = max_idle_time
            if connect_timeout:
                args['connect_timeout'] = connect_timeout
            args.update(kwargs)
            connection_pool = ConnectionPool(Connection, **args)
        self.connection_pool = connection_pool

    def __repr__(self):
        return 'SQLClient id: %d, pool: %s' % (id(self), self.connection_pool)

    def ping(self):
        pool = self.connection_pool
        connection = pool.get_connection()
        context = connection.get_connect_context()
        try:
            context.ping()
        finally:
            pool.release(connection)

    def query(self, query, *parameters, **kwparameters):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.query(query, *parameters, **kwparameters)
        except OperationalError as e:
            connection.close()
            return connection.query(query, *parameters, **kwparameters)
        finally:
            pool.release(connection)

    def execute(self, query, *parameters, **kwparameters):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.execute(query, *parameters, **kwparameters)
        except OperationalError as e:
            connection.close()
            return connection.execute(query, *parameters, **kwparameters)
        finally:
            pool.release(connection)

    def update(self, query, *parameters, **kwparameters):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.update(query, *parameters, **kwparameters)
        except OperationalError as e:
            connection.close()
            return connection.update(query, *parameters, **kwparameters)
        finally:
            pool.release(connection)

    def insert(self, query, *parameters, **kwparameters):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.insert(query, *parameters, **kwparameters)
        except OperationalError as e:
            connection.close()
            return connection.insert(query, *parameters, **kwparameters)
        finally:
            pool.release(connection)

    def executemany(self, query, parameters):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.executemany(query, parameters)
        except OperationalError as e:
            connection.close()
            return connection.insert(query, parameters)
        finally:
            pool.release(connection)

    def executemany_rowcount(self, query, parameter):
        pool = self.connection_pool
        connection = pool.get_connection()
        try:
            return connection.executemany_rowcount(query, parameter)
        except OperationalError as e:
            connection.close()
            return connection.executemany_rowcount(query, parameter)
        finally:
            pool.release(connection)

    def disconnect(self):
        self.connection_pool.disconnect()

    def __del__(self):
        self.connection_pool.disconnect()

    def transation(self):
        return Transaction(self.connection_pool)


class Transaction(object):

    def __init__(self, connection_pool):
        self.connection_pool = weakref.proxy(connection_pool)
        self.connection = None
        self.reset()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.reset()

    def __del__(self):
        try:
            self.reset()
        except:
            pass

    def reset(self):
        if self.connection:
            self.connection_pool.release(self.connection)
            self.connection = None

    def begin(self):
        try:
            if not self.connection:
                self.connection = self.connection_pool.get_connection()
                self.connection._ensure_connected()
            context = self.connection.get_connect_context()
            context.ping()
            context.begin()
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            if self.connection:
                self.connection.close()

    def commit(self):
        try:
            context = self.connection.get_connect_context(False)
            context.commit()
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            if self.connection:
                self.connection.close()

    def rollback(self):
        if not self.connection:
            raise AttributeError('Transaction not have began')
        try:
            context = self.connection.get_connect_context(False)
            context.rollback()
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            if self.connection:
                self.connection.close()

    def _execute(self, cursor, query, parameters, kwparameters):
        try:
            return cursor.execute(query, kwparameters or parameters)
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            self.connection.close()
            raise

    def query(self, query, *parameters, **kwparameters):
        cursor = self.connection._cursor()
        try:
            self._execute(cursor, query, parameters, kwparameters)
            column_names = [d[0] for d in cursor.description]
            return [Row(zip(column_names, row)) for row in cursor]
        finally:
            cursor.close()

    def execute(self, query, *parameters, **kwparameters):
        cursor = self.connection._cursor()
        try:
            self._execute(cursor, query, parameters, kwparameters)
        finally:
            cursor.close()

    def update(self, query, *parameters, **kwparameters):
        cursor = self.connection._cursor()
        try:
            self._execute(cursor, query, parameters, kwparameters)
            return cursor.rowcount
        finally:
            cursor.close()

    def insert(self, query, *parameters, **kwparameters):
        cursor = self.connection._cursor()
        try:
            self._execute(cursor, query, parameters, kwparameters)
            return cursor.lastrowid
        finally:
            cursor.close()

    def executemany_lastrowid(self, query, parameters):
        """Executes the given query against all the given param sequences.

        We return the lastrowid from the query.
        """
        cursor = self.connection._cursor()
        try:
            cursor.executemany(query, parameters)
            return cursor.lastrowid
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            self.connection.close()
            raise
        finally:
            cursor.close()

    def executemany(self, query, parameters):
        """Executes the given query against all the given param sequences.

        We return the lastrowid from the query.
        """
        return self.executemany_lastrowid(query, parameters)

    def executemany_rowcount(self, query, parameters):
        """Executes the given query against all the given param sequences.

        We return the rowcount from the query.
        """
        cursor = self.connection._cursor()
        try:
            cursor.executemany(query, parameters)
            return cursor.rowcount
        except OperationalError:
            logging.error("Error connecting to MySQL on %s",
                          self.connection_pool)
            self.connection.close()
            raise
        finally:
            cursor.close()
